package com.ruoyi.project.business.monthsalary.service.impl;

import com.alibaba.fastjson.JSON;
import com.ruoyi.common.exception.CustomException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.project.business.askleave.request.ListAskLeaveRequest;
import com.ruoyi.project.business.askleave.response.AskLeaveResponse;
import com.ruoyi.project.business.askleave.service.IAskLeaveService;
import com.ruoyi.project.business.monthsalary.SalaryDomain;
import com.ruoyi.project.business.monthsalary.entity.MonthSalary;
import com.ruoyi.project.business.monthsalary.mapper.MonthSalaryMapper;
import com.ruoyi.project.business.monthsalary.request.CulMonthSalaryRequest;
import com.ruoyi.project.business.monthsalary.request.ListMonthSalaryRequest;
import com.ruoyi.project.business.monthsalary.response.MonthSalaryResponse;
import com.ruoyi.project.business.monthsalary.service.IMonthSalaryService;
import com.ruoyi.project.business.salarydetail.request.AddSalaryDetailRequest;
import com.ruoyi.project.business.salarydetail.service.ISalaryDetailService;
import com.ruoyi.project.business.salaryoptions.entity.SalaryOptions;
import com.ruoyi.project.business.salaryoptions.service.ISalaryOptionsService;
import com.ruoyi.project.business.salaryrule.entity.MonthSalaryRule;
import com.ruoyi.project.business.salaryrule.service.IMonthSalaryRuleService;
import com.ruoyi.project.business.tenantsocialsecurity.entity.TenantsocialSecurity;
import com.ruoyi.project.business.tenantsocialsecurity.service.ITenantsocialSecurityService;
import com.ruoyi.project.business.utils.BusinessUtil;
import com.ruoyi.project.system.entity.SysUser;
import com.ruoyi.project.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 月薪Service业务层处理
 * 
 * @author ${author}
 * @date 2024-02-20
 */
@Slf4j
@Service
public class MonthSalaryServiceImpl implements IMonthSalaryService {
    @Autowired
    private MonthSalaryMapper monthSalaryMapper;
    @Autowired
    private ISysUserService userService;
    @Autowired
    private IMonthSalaryRuleService salaryRuleService;
    @Autowired
    private ISalaryOptionsService optionsService;
    @Autowired
    private ITenantsocialSecurityService tenantsocialSecurityService;
    @Autowired
    private ISalaryDetailService salaryDetailService;
    @Autowired
    private IAskLeaveService askLeaveService;

    /**
     * 查询月薪
     * 
     * @param id 月薪ID
     * @return 月薪
     */
    @Override
    public MonthSalary selectMonthSalaryById(Long id) {
        return monthSalaryMapper.selectMonthSalaryById(id);
    }

    /**
     * 查询月薪列表
     * 
     * @return 月薪
     */
    @Override
    public List<MonthSalaryResponse> selectMonthSalaryList(ListMonthSalaryRequest request, Long tenantId) {
        Long deptId = request.getDeptId();
        Long userId = request.getUserId();
        List<SysUser> users = userService.findSysUsersByUserIdOrDeptId(userId, deptId, tenantId);

        if(CollectionUtils.isNotEmpty(users)) {
            Date yearMonth = request.getYearMonth();
            Integer year = DateUtils.year(yearMonth);
            Integer month = DateUtils.month(yearMonth);
            Date dueDate = request.getDueDate();
            Date payDate = request.getPayDate();
            List<MonthSalaryResponse> monthSalaryResponses = BeanUtils.copyList(users, MonthSalaryResponse.class);

            List<Long> userIds = users.stream().map(SysUser::getUserId).distinct().collect(Collectors.toList());
            if(CollectionUtils.isNotEmpty(userIds)) {
                // 理论上来讲一个月就应该只有一条记录
                List<MonthSalary> monthSalaries = monthSalaryMapper.selectMonthSalaryListOfMonth(year, month, userIds, tenantId);
                Map<Long, MonthSalary> userMonthSalaryMap = monthSalaries.stream().collect(Collectors.toMap(MonthSalary::getUserId, Function.identity(), (a, b) -> a));
                fillSalary(monthSalaryResponses, userMonthSalaryMap);
            }

            return monthSalaryResponses;
        }
        return new ArrayList<>();

    }

    private void fillSalary(List<MonthSalaryResponse> monthSalaryResponses, Map<Long, MonthSalary> userMonthSalaryMap) {
        monthSalaryResponses.forEach(tmp -> {
            if(userMonthSalaryMap.containsKey(tmp.getUserId())) {
                MonthSalary monthSalary = userMonthSalaryMap.get(tmp.getUserId());
                tmp.setMonth(monthSalary.getMonth());
                tmp.setId(monthSalary.getId());
                tmp.setYear(monthSalary.getYear());
                tmp.setAmount(monthSalary.getAmount());
                tmp.setPayDate(monthSalary.getPayDate());
                tmp.setDueDate(monthSalary.getDueDate());
            }
        });
    }

    /**
     * 新增月薪
     * 
     * @param monthSalary 月薪
     * @return 结果
     */
    @Override
    public int insertMonthSalary(MonthSalary monthSalary, Long operatorId, Long tenantId) {
        MonthSalary existed = existEntity(monthSalary.getUserId(), monthSalary.getYear(), monthSalary.getMonth(), tenantId);
        if(existed == null) {
            monthSalary.setCreateTime(DateUtils.getNowDate());
            monthSalary.setTenantId(tenantId);
            monthSalary.setCreator(operatorId);
            monthSalary.setUpdater(operatorId);
            monthSalary.setUpdateTime(monthSalary.getCreateTime());
            monthSalary.setDeleted(Boolean.FALSE);
            return monthSalaryMapper.insertMonthSalary(monthSalary);
        }
        throw new CustomException("本月工资(" + monthSalary.getYear() + "-" + monthSalary.getMonth() + ")已完成计算");
    }

    private MonthSalary existEntity(Long userId, Integer year, Integer month, Long tenantId) {
        return monthSalaryMapper.selectMonthSalaryList(userId, year, month, tenantId);
    }

    /**
     * 修改月薪
     * 
     * @param monthSalary 月薪
     * @return 结果
     */
    @Override
    public int updateMonthSalary(MonthSalary monthSalary, Long operatorId, Long tenantId) {
        monthSalary.setUpdateTime(DateUtils.getNowDate());
        monthSalary.setTenantId(tenantId);
        monthSalary.setUpdater(operatorId);
        return monthSalaryMapper.updateMonthSalary(monthSalary);
    }

    /**
     * 批量删除月薪
     * 
     * @param ids 需要删除的月薪ID
     * @return 结果
     */
    @Override
    public int deleteMonthSalaryByIds(Long[] ids, Long operatorId, Long tenantId) {
        return monthSalaryMapper.deleteMonthSalaryByIds(ids);
    }

    /**
     * 删除月薪信息
     * 
     * @param id 月薪ID
     * @return 结果
     */
    @Override
    public int deleteMonthSalaryById(Long id, Long operatorId, Long tenantId) {
        return monthSalaryMapper.deleteMonthSalaryById(id);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public BigDecimal culUserSalary(CulMonthSalaryRequest request, Long tenantId, Long operatorId) {

        Long userId = request.getUserId();

        BigDecimal resultsalary = null;
        if(userId == null) {
            throw new CustomException("选择一个员工再计算");
        }
        SysUser user = userService.findUsersOfUserId(userId, tenantId);
        if(user == null) {
            log.error("未知的员工 {}", userId);
            throw new CustomException("异常员工信息");
        }
        //
        MonthSalaryRule rule = salaryRuleService.selectRuleByUserId(userId, tenantId);
        if(rule == null) {
            // 再试试部门规则
            log.error("员工 {} 未配置个人算薪规则，", userId);
            throw new CustomException("员工未配置薪资配置");
        }

        Date yearMonth = request.getYearMonth();
        Integer year = DateUtils.year(yearMonth);
        Integer month = DateUtils.month(yearMonth);
        // 判断当月有没有记录
        MonthSalary monthSalary = selectMonthSalaryByUserId(userId, year, month, tenantId);
        if(monthSalary != null) {
            // 如果已存在的先删除
            deleteMonthSalaryById(monthSalary.getId(), operatorId, tenantId);
            salaryDetailService.deleteMonthSalaryDetailByMonthSalaryId(monthSalary.getId(), operatorId, tenantId);
        }


        ListAskLeaveRequest param = new ListAskLeaveRequest();
        param.setYearMonth(yearMonth);
        param.setUserId(userId);
        List<AskLeaveResponse> askLeaveResponses = askLeaveService.selectAskLeaveList(param, tenantId);

        SalaryDomain salaryDomain = SalaryDomain.builder()
                .askLeaveResponses(askLeaveResponses)
                .optionsService(optionsService)
                .operatorId(operatorId)
                .tenantId(tenantId)
                .request(request)
                .rule(rule)
                .tenantsocialSecurityService(tenantsocialSecurityService)
                .build()
                .salary()
                ;
        MonthSalary entity = salaryDomain.getResultMonthSalary();
        this.insertMonthSalary(entity, operatorId, tenantId);

        AddSalaryDetailRequest salaryDetail = salaryDomain.getResultSalaryDetail();
        salaryDetail.setSalaryId(entity.getId()); // 确认下是否能拿到
        salaryDetailService.insertSalaryDetail(salaryDetail, operatorId, tenantId);


        return resultsalary;
    }

    private MonthSalary selectMonthSalaryByUserId(Long userId, Integer year, Integer month, Long tenantId) {
        List<MonthSalary> monthSalaries = monthSalaryMapper.selectMonthSalaryListOfMonth(year, month, Arrays.asList(userId), tenantId);
        if(CollectionUtils.isNotEmpty(monthSalaries)) {
            return monthSalaries.get(0);
        }
        return null;
    }

    private BigDecimal culSocialSecurity(Long tenantId, Integer year, Long userId) {
        BigDecimal socialSecurity = BigDecimal.ZERO;
        TenantsocialSecurity tenantsocialSecurity = tenantsocialSecurityService.selectByUser(year, userId, tenantId);
        if(tenantsocialSecurity != null) {
            BigDecimal baseAmt = tenantsocialSecurity.getBaseAmt();
            if (baseAmt != null) {
                // 计算社保
                socialSecurity = baseAmt.multiply(new BigDecimal(tenantsocialSecurity.getPercent())).divide(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP);
            }
        }
        return socialSecurity;
    }

    @Override
    public void culDeptSalary(CulMonthSalaryRequest request, Long tenantId, Long operatorId) {
        // 薪资计算规则 config
        Long deptId = request.getDeptId();
        if(deptId != null) {
            //
            Date yearMonth = request.getYearMonth();
            culOfDeptId(deptId, yearMonth, tenantId, operatorId);
        } else {
            throw new CustomException("选择一个部门再计算");
        }
    }

    private void culOfDeptId(Long deptId, Date yearMonth, Long tenantId, Long operatorId) {
        List<SysUser> users = userService.findUsersOfDeptId(deptId, tenantId);
        if(CollectionUtils.isNotEmpty(users)) {

            Integer year = DateUtils.year(yearMonth);
            Integer month = DateUtils.month(yearMonth);
            // 查 user 配置的规则
            List<Long> userIds = users.stream().map(SysUser::getUserId).distinct().collect(Collectors.toList());
            Map<Long, MonthSalaryRule> ruleMap = salaryRuleService.selectRuleByUserIds(userIds, tenantId);

            for (SysUser user : users) {
                Long userId = user.getUserId();
                MonthSalaryRule rule = ruleMap.get(userId);
                String formula = rule.getFormula();
                if(StringUtils.isNotBlank(formula)) {
                    BigDecimal salary = doSingleCul( formula, tenantId);

                    MonthSalary entity = new MonthSalary();
                    entity.setMonth(month);
                    entity.setYear(year);
                    entity.setAmount(salary);
                    entity.setDueDate(rule.getDueDate());
                    entity.setPayDate(rule.getPayDate());
                    entity.setUserId(userId);
                    this.insertMonthSalary(entity, operatorId, tenantId);
                    log.info("员工{}工资计算完成 : {}", userId, JSON.toJSONString(entity));
                }

            }
        }
    }

    private BigDecimal culOfUserId(String formula, Long userId, Long tenantId) {

        SysUser user = userService.findUsersOfUserId(userId, tenantId);
        if(user == null) {
            //
            log.error("未知的员工 {}", userId);
            throw new CustomException("异常员工信息");
        }

        // 应发
        BigDecimal salary = doSingleCul( formula, tenantId);

        return salary;
    }

    private BigDecimal doSingleCul(String formula, Long tenantId) {
        // 从 formula 中解析出计算项
        List<Long> optionIds = extractOptions(formula);
        List<SalaryOptions> salaryOptions = optionsService.selectSalaryOptionsByIds(optionIds, tenantId);
        if(CollectionUtils.isEmpty(salaryOptions) || salaryOptions.size() != optionIds.size()) {
            log.error("计算项异常：存在未知的计算项 {}", formula);
            throw new CustomException("计算项异常：存在未知的计算项");
        }
        //  执行计算
        for (SalaryOptions opt : salaryOptions) {
            formula = StringUtils.replace(formula, "{" + opt.getId() + "}", opt.getAmount().toString());
        }
        BigDecimal bigDecimal = BusinessUtil.culBySpring(formula);
        return bigDecimal;
    }

    private List<Long> extractOptions(String formula) {
        Pattern pattern = Pattern.compile("\\{([^}]*)\\}");
        Matcher matcher = pattern.matcher(formula);

        List<Long> res = new ArrayList<>();
        while (matcher.find()) {
            System.out.println(matcher.group(1));
            String match = matcher.group(1);
            if(StringUtils.isBlank(match)) {
                log.error("错误的公式 {}", formula);
                throw new CustomException("错误的公式：" + formula);
            }
            res.add(Long.valueOf(match));
        }
        if(res.size() <= 0) {
            throw new CustomException("错误的公式：" + formula);
        }
        return res;
    }
}
